{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8221c040",
   "metadata": {},
   "source": [
    "# How to add runtime configuration to your graph\n",
    "\n",
    "Once you've created an app in LangGraph, you likely will want to permit\n",
    "configuration at runtime.\n",
    "\n",
    "For instance, you may want to let the LLM or prompt be selected dynamically,\n",
    "configure a user's `user_id` to enforce row-level security, etc.\n",
    "\n",
    "In LangGraph, configuration and other\n",
    "[\"out-of-band\" communication](https://en.wikipedia.org/wiki/Out-of-band) is done\n",
    "via the\n",
    "[RunnableConfig](https://v02.api.js.langchain.com/interfaces/langchain_core_runnables.RunnableConfig.html),\n",
    "which is always the second positional arg when invoking your application.\n",
    "\n",
    "Below, we walk through an example of letting you configure a user ID and pick\n",
    "which model to use.\n",
    "\n",
    "## Setup\n",
    "\n",
    "This guide will use Anthropic's Claude 3 Haiku and OpenAI's GPT-4o model. We\n",
    "will optionally set our API key for\n",
    "[LangSmith tracing](https://smith.langchain.com/), which will give us\n",
    "best-in-class observability."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "f0dcd657",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Configuration: LangGraphJS\n"
     ]
    }
   ],
   "source": [
    "// process.env.OPENAI_API_KEY = \"sk_...\";\n",
    "\n",
    "// Optional, add tracing in LangSmith\n",
    "// process.env.LANGCHAIN_API_KEY = \"ls__...\";\n",
    "// process.env.LANGCHAIN_CALLBACKS_BACKGROUND = \"true\";\n",
    "process.env.LANGCHAIN_TRACING_V2 = \"true\";\n",
    "process.env.LANGCHAIN_PROJECT = \"Configuration: LangGraphJS\";"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a04f018e",
   "metadata": {},
   "source": [
    "## Define the graph\n",
    "\n",
    "We will create an exceedingly simple message graph for this example.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "bdf2fe0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "import { BaseMessage } from \"@langchain/core/messages\";\n",
    "import { ChatOpenAI } from \"@langchain/openai\";\n",
    "import { ChatAnthropic } from \"@langchain/anthropic\";\n",
    "import { ChatPromptTemplate } from \"@langchain/core/prompts\";\n",
    "import { RunnableConfig } from \"@langchain/core/runnables\";\n",
    "import {\n",
    "  END,\n",
    "  START,\n",
    "  StateGraph,\n",
    "  Annotation,\n",
    "} from \"@langchain/langgraph\";\n",
    "\n",
    "const AgentState = Annotation.Root({\n",
    "  messages: Annotation<BaseMessage[]>({\n",
    "    reducer: (x, y) => x.concat(y),\n",
    "  }),\n",
    "  userInfo: Annotation<string | undefined>({\n",
    "    reducer: (x, y) => {\n",
    "      return y ? y : x ? x : \"N/A\";\n",
    "    },\n",
    "    default: () => \"N/A\",\n",
    "  })\n",
    "});\n",
    "\n",
    "const promptTemplate = ChatPromptTemplate.fromMessages([\n",
    "  [\"system\", \"You are a helpful assistant.\\n\\n## User Info:\\n{userInfo}\"],\n",
    "  [\"placeholder\", \"{messages}\"],\n",
    "]);\n",
    "\n",
    "const callModel = async (\n",
    "  state: typeof AgentState.State,\n",
    "  config?: RunnableConfig,\n",
    ") => {\n",
    "  const { messages, userInfo } = state;\n",
    "  const modelName = config?.configurable?.model;\n",
    "  const model = modelName === \"claude\"\n",
    "    ? new ChatAnthropic({ model: \"claude-3-haiku-20240307\" })\n",
    "    : new ChatOpenAI({ model: \"gpt-4o\" });\n",
    "  const chain = promptTemplate.pipe(model);\n",
    "  const response = await chain.invoke(\n",
    "    {\n",
    "      messages,\n",
    "      userInfo,\n",
    "    },\n",
    "    config,\n",
    "  );\n",
    "  return { messages: [response] };\n",
    "};\n",
    "\n",
    "const fetchUserInformation = async (\n",
    "  _: typeof AgentState.State,\n",
    "  config?: RunnableConfig,\n",
    ") => {\n",
    "  const userDB = {\n",
    "    user1: {\n",
    "      name: \"John Doe\",\n",
    "      email: \"jod@langchain.ai\",\n",
    "      phone: \"+1234567890\",\n",
    "    },\n",
    "    user2: {\n",
    "      name: \"Jane Doe\",\n",
    "      email: \"jad@langchain.ai\",\n",
    "      phone: \"+0987654321\",\n",
    "    },\n",
    "  };\n",
    "  const userId = config?.configurable?.user;\n",
    "  if (userId) {\n",
    "    const user = userDB[userId as keyof typeof userDB];\n",
    "    if (user) {\n",
    "      return {\n",
    "        userInfo:\n",
    "          `Name: ${user.name}\\nEmail: ${user.email}\\nPhone: ${user.phone}`,\n",
    "      };\n",
    "    }\n",
    "  }\n",
    "  return { userInfo: \"N/A\" };\n",
    "};\n",
    "\n",
    "const workflow = new StateGraph(AgentState)\n",
    "  .addNode(\"fetchUserInfo\", fetchUserInformation)\n",
    "  .addNode(\"agent\", callModel)\n",
    "  .addEdge(START, \"fetchUserInfo\")\n",
    "  .addEdge(\"fetchUserInfo\", \"agent\")\n",
    "  .addEdge(\"agent\", END);\n",
    "\n",
    "const graph = workflow.compile();"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ae55d0e",
   "metadata": {},
   "source": [
    "## Call with config\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ca608969",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Could you remind me of my email??\n",
      "-----\n",
      "\n",
      "Could you remind me of my email??\n",
      "-----\n",
      "\n",
      "Your email is jod@langchain.ai.\n",
      "-----\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import { HumanMessage } from \"@langchain/core/messages\";\n",
    "\n",
    "const config = {\n",
    "  configurable: {\n",
    "    model: \"openai\",\n",
    "    user: \"user1\",\n",
    "  },\n",
    "};\n",
    "const inputs = {\n",
    "  messages: [new HumanMessage(\"Could you remind me of my email??\")],\n",
    "};\n",
    "for await (\n",
    "  const { messages } of await graph.stream(inputs, {\n",
    "    ...config,\n",
    "    streamMode: \"values\",\n",
    "  })\n",
    ") {\n",
    "  let msg = messages[messages?.length - 1];\n",
    "  if (msg?.content) {\n",
    "    console.log(msg.content);\n",
    "  } else if (msg?.tool_calls?.length > 0) {\n",
    "    console.log(msg.tool_calls);\n",
    "  } else {\n",
    "    console.log(msg);\n",
    "  }\n",
    "  console.log(\"-----\\n\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1afdf011",
   "metadata": {},
   "source": [
    "## Change the config\n",
    "\n",
    "Now let's try the same input with a different user."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "e568e8e3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Could you remind me of my email??\n",
      "-----\n",
      "\n",
      "Could you remind me of my email??\n",
      "-----\n",
      "\n",
      "Your email address is jad@langchain.ai.\n",
      "-----\n",
      "\n"
     ]
    }
   ],
   "source": [
    "const config2 = {\n",
    "  configurable: {\n",
    "    model: \"openai\",\n",
    "    user: \"user2\",\n",
    "  },\n",
    "};\n",
    "const inputs2 = {\n",
    "  messages: [new HumanMessage(\"Could you remind me of my email??\")],\n",
    "};\n",
    "for await (\n",
    "  const { messages } of await graph.stream(inputs2, {\n",
    "    ...config2,\n",
    "    streamMode: \"values\",\n",
    "  })\n",
    ") {\n",
    "  let msg = messages[messages?.length - 1];\n",
    "  if (msg?.content) {\n",
    "    console.log(msg.content);\n",
    "  } else if (msg?.tool_calls?.length > 0) {\n",
    "    console.log(msg.tool_calls);\n",
    "  } else {\n",
    "    console.log(msg);\n",
    "  }\n",
    "  console.log(\"-----\\n\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f000b97c",
   "metadata": {},
   "source": [
    "Check out the\n",
    "[LangSmith Trace (link)](https://smith.langchain.com/public/bbd3561f-c0d1-4886-ae18-a6626c6b8670/r/946098b5-84d3-4456-a03c-5dbc8591e76b)\n",
    "for this run to \"see what the LLM sees\".\n",
    "\n",
    "## Config schema\n",
    "\n",
    "You can also pass an annotation defining the shape of `config.configurable` into your graph. This will currently only expose type information on the compiled graph, and will not filter out keys:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "1f703d69",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Expected I am expected\n",
      "Unexpected I am unexpected but present\n"
     ]
    }
   ],
   "source": [
    "import { MessagesAnnotation } from \"@langchain/langgraph\";\n",
    "\n",
    "const ConfigurableAnnotation = Annotation.Root({\n",
    "  expectedField: Annotation<string>,\n",
    "});\n",
    "\n",
    "const printNode = async (\n",
    "  state: typeof MessagesAnnotation.State,\n",
    "  config: RunnableConfig<typeof ConfigurableAnnotation.State>\n",
    ") => {\n",
    "  console.log(\"Expected\", config.configurable?.expectedField);\n",
    "  // @ts-expect-error This type will be present even though is not in the typing\n",
    "  console.log(\"Unexpected\", config.configurable?.unexpectedField);\n",
    "  return {};\n",
    "};\n",
    "\n",
    "const graphWithConfigSchema = new StateGraph(MessagesAnnotation, ConfigurableAnnotation)\n",
    "  .addNode(\"printNode\", printNode)\n",
    "  .addEdge(START, \"printNode\")\n",
    "  .compile();\n",
    "\n",
    "const result = await graphWithConfigSchema.invoke({\n",
    "  messages: [{ role: \"user\", content: \"Echo!\"} ]\n",
    "}, { configurable: { expectedField: \"I am expected\", unexpectedField: \"I am unexpected but present\" } });"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d55f98e1",
   "metadata": {},
   "source": [
    "```\n",
    "```"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "TypeScript",
   "language": "typescript",
   "name": "tslab"
  },
  "language_info": {
   "codemirror_mode": {
    "mode": "typescript",
    "name": "javascript",
    "typescript": true
   },
   "file_extension": ".ts",
   "mimetype": "text/typescript",
   "name": "typescript",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
